package security.filters;

import framework.config.SecurityConfig;
import framework.security.Account;
import framework.security.FunctionPermission;
import framework.security.token.AuthTokenInfo;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.StringUtils;
import security.defined.AuthenticationCheckerResult;
import security.error.AuthenticationCheckerException;
import security.service.FunctionPermissionLoader;
import security.service.PathMatcher;
import security.service.SecurityContext;
import security.utils.TokenChannel;
import security.utils.TokenDetail;

import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Base64;
import java.util.Date;
import java.util.List;

@Slf4j
public abstract class AuthenticationBase {

    @Getter
    private final SecurityContext securityContext;
    @Getter
    private final SecurityConfig securityConfig;
    @Getter
    private final PathMatcher authPathMatcher;
    @Getter
    protected boolean corsDisabled;

    public AuthenticationBase(SecurityContext securityContext) {
        this.securityContext = securityContext;
        this.securityConfig = securityContext.getSecurityConfig();
        this.corsDisabled = securityContext.getSecurityConfig().getCorsAuth() == false;
        //
        List<FunctionPermission> functionPermissionList = this.loadFunctionPermissionList();
        this.appendSecurityConfig(functionPermissionList);
        this.authPathMatcher = new PathMatcher(this.convertFunctionPermissionHandler(functionPermissionList));
    }

    /**
     * 将功能函数列表转换为处理器列表
     *
     * @param functionPermissionList
     * @return
     */
    protected List<FunctionPermissionHandler> convertFunctionPermissionHandler(List<FunctionPermission> functionPermissionList) {
        List<FunctionPermissionHandler> list = new ArrayList<>();
        for (FunctionPermission functionPermission : functionPermissionList) {
            list.add(new FunctionPermissionHandler(functionPermission));
        }
        return list;
    }

    /**
     * 将配置文件中的配置项附加到功能列表
     *
     * @param functionPermissionList
     */
    protected void appendSecurityConfig(List<FunctionPermission> functionPermissionList) {
        String pathAnonList = getSecurityConfig().getPathAnonList();
        String pathAuthList = getSecurityConfig().getPathAuthList();
        if (pathAnonList != null) {
            for (String path : pathAnonList.split(",")) {
                functionPermissionList.add(new FunctionPermission(path, "ANON", null));
            }
        }
        if (pathAuthList != null) {
            for (String path : pathAuthList.split(",")) {
                functionPermissionList.add(new FunctionPermission(path, "AUTH", null));
            }
        }
    }

    /**
     * 执行授权检查
     *
     * @param handler
     */
    public AuthenticationData authCheck(AuthenticationHandler handler) throws AuthenticationCheckerException {

        // cors check
        AuthenticationData corsData = this.checkCors(handler);
        if (corsData != null) {
            return corsData;
        }

        // 获取token
        TokenDetail tokenDetail = handler.getRequestToken();
        AuthTokenInfo tokenInfo = null;
        String secret = "";
        long id = 0L;
        long sid = 0L;

        // token check
        if (tokenDetail != null) {
            // token parse
            switch (tokenDetail.getChannel()) {
                case Basic:
                    Account account = this.basicParser(tokenDetail.getToken());
                    id = account.getId();
                    // 该种授权方式不提供明文密码，使用掩码返回
                    secret = "Password: ******";
                    break;
                case Bearer:
                case Query:
                case Cookie:
                    tokenInfo = this.tokenParser(tokenDetail.getToken());
                    if (tokenInfo != null && !tokenInfo.isExpired() && this.validateSid(tokenInfo)) {
                        id = tokenInfo.getId();
                        sid = tokenInfo.getSid();
                        secret = tokenDetail.getSecret();
                    } else {
                        tokenInfo = null;
                    }
                    break;
//                case OAuth:
                default:
                    break;
            }
        }

        // 加载功能
        FunctionPermissionHandler functionPermissionHandler = this.findFunctionMatcher(handler);
        if (functionPermissionHandler == null) {
            // 请求的路径在配置中不存在，强制要求授权
            throw new AuthenticationCheckerException(AuthenticationCheckerResult.NO_PERMISSION);
        }

        // 匿名授权
        if (this.hasAnonymous(handler, functionPermissionHandler)) {
            return new AuthenticationData(0L, "", 0L);
        }

        // 放行判定
        if (functionPermissionHandler.hasPermission("PERMIT")) {
            // PERMIT | 允许已登录或未登录的状态方问
            return new AuthenticationData(id, secret, sid);
        }

        // 未登录
        if (id < 1L) {
            throw new AuthenticationCheckerException(AuthenticationCheckerResult.NO_AUTH);
        }

        // --- 账户验证之后 之后 --- //

        // 过期判定
        if (tokenInfo != null) {
            // 过期提醒输出
            this.tokenReminder(tokenInfo, handler);
            // 过期余量输出
            this.tokenBalance(tokenInfo, handler);
        }

        // 更新TOKEN
        if (tokenInfo != null && tokenDetail.getChannel() == TokenChannel.Cookie) {
            this.updateTokenToCookie(tokenInfo, handler);
        }

        // 授权要求
        if (functionPermissionHandler.hasPermission("AUTH")) {
            return new AuthenticationData(id, secret, sid);
        }

        // --- 权限验证 --- //

        // 账号权限加载
        List<String> permissions = getSecurityContext().getAccountLoader().loadPermissions(id);
        // 权限验证
        if (!this.hasPermissions(handler, permissions, functionPermissionHandler)) {
            throw new AuthenticationCheckerException(AuthenticationCheckerResult.NO_PERMISSION);
        }

        //
        return new AuthenticationData(id, secret, sid);
    }

    /**
     * 令牌更新
     *
     * @param tokenInfo
     * @param handler
     */
    protected void updateTokenToCookie(AuthTokenInfo tokenInfo, AuthenticationHandler handler) {
        if (getSecurityConfig().getCookieTokenRenew() == null) {
            return;
        }
        if (getSecurityConfig().getCookieTokenRenew() == 0) {
            return;
        }
        if (getSecurityConfig().getCookieTokenRenew() > 99 || getSecurityConfig().getCookieTokenRenew() < 1) {
            throw new RuntimeException("cookie-token-renew value allow 1-99, suggest config is 60");
        }
        long margin = (tokenInfo.getExpireTime().getTime() - new Date().getTime()) / 1000;
        if (margin > 0) {
            double threshold = tokenInfo.getDuration() * (getSecurityConfig().getCookieTokenRenew() / 100.0d);
            if (tokenInfo.getDuration() - margin > threshold) {
                AuthTokenInfo newToken = this.createNewTokenOfCookieUpdate(tokenInfo);
                String token = getSecurityContext().getTokenBuilder().encode(newToken);
                handler.saveTokenToCookie(token);
            }
        }
    }

    /**
     * 创建用于更新cookie中token的新token
     *
     * @param tokenInfo
     * @return
     */
    protected AuthTokenInfo createNewTokenOfCookieUpdate(AuthTokenInfo tokenInfo) {
        return getSecurityContext().getTokenGenerator().updateToken(tokenInfo);
    }

    protected boolean validateSid(AuthTokenInfo tokenInfo) {
        // 默认不对会话号进行验证
        return true;
    }

    protected AuthenticationData checkCors(AuthenticationHandler handler) {
        String requestMethod = handler.getRequestMethod();
        if ("OPTIONS".equals(requestMethod)) {
            return new AuthenticationData(0L, "", 0L);
        }
        return null;
    }

    protected void tokenBalance(AuthTokenInfo tokenInfo, AuthenticationHandler handler) {
        String tokenBalanceOutputHeader = getSecurityConfig().getTokenBalanceOutputHeader();
        if (StringUtils.hasText(tokenBalanceOutputHeader)) {
            long sec = (tokenInfo.getExpireTime().getTime() - new Date().getTime()) / 1000;
            handler.tokenBalanceOutput((int) sec);
        }
    }

    /**
     * 过期提醒判定
     *
     * @param tokenInfo
     * @param handler
     */
    protected void tokenReminder(AuthTokenInfo tokenInfo, AuthenticationHandler handler) {
        Integer tokenExpirationReminderSeconds = getSecurityConfig().getTokenExpirationReminderSeconds();
        Double tokenExpirationReminderPercent = getSecurityConfig().getTokenExpirationReminderPercent();
        // 过期时间
        if (tokenInfo.getExpireTime() != null
                && tokenExpirationReminderSeconds != null && tokenExpirationReminderSeconds > 0) {
            long sec = (tokenInfo.getExpireTime().getTime() - new Date().getTime()) / 1000;
            if (sec < tokenExpirationReminderSeconds) {
                handler.tokenExpiredReminder((int) sec);
            }
        } else if (tokenInfo.getExpireTime() != null
                && tokenInfo.getDuration() > 0
                && tokenExpirationReminderPercent != null && tokenExpirationReminderPercent > 0.0D) {
            long sec = (tokenInfo.getExpireTime().getTime() - new Date().getTime()) / 1000;
            if (sec > 0) {
                double threshold = tokenInfo.getDuration() * (tokenExpirationReminderPercent / 100.0d);
                if (tokenInfo.getDuration() - sec > threshold) {
                    handler.tokenExpiredReminder((int) sec);
                }
            }
        }
    }


    /**
     * 未授权
     *
     * @param handler
     * @param functionPermissionHandler
     */
    protected boolean hasAnonymous(AuthenticationHandler handler, FunctionPermissionHandler functionPermissionHandler) {
        return functionPermissionHandler.hasPermission("ANON");
    }

    /**
     * 权限检查
     *
     * @param handler
     * @param permissions
     * @param functionPermissionHandler
     * @return
     */
    protected boolean hasPermissions(AuthenticationHandler handler, List<String> permissions, FunctionPermissionHandler functionPermissionHandler) {
        if (permissions == null) return false;
        if (permissions.size() == 0) return false;
        //
        for (String permission : permissions) {
            if (permission != null && functionPermissionHandler.hasPermission(permission)) {
                return true;
            }
        }
        //
        return false;
    }

    /**
     * Token解析器
     *
     * @param token
     * @return
     */
    protected AuthTokenInfo tokenParser(String token) {
        if (!StringUtils.hasText(token)) {
            return null;
        }
        AuthTokenInfo tokenInfo;
        try {
            tokenInfo = getSecurityContext().getTokenBuilder().decode(token);
        } catch (Exception ex) {
            tokenInfo = null;
        }
        return tokenInfo;
    }

    /**
     * Token解析器
     *
     * @param token
     * @return
     */
    protected Account basicParser(String token) {
        if (!StringUtils.hasText(token)) {
            return null;
        }
        Account account = null;
        try {
            byte[] decode = Base64.getDecoder().decode(token);
            String s = new String(decode, StandardCharsets.UTF_8);
            String[] strings = s.split(":", 2);
            if (strings.length == 2) {
                try {
                    account = getSecurityContext().getAccountChecker().authCheck(strings[0], strings[1], null);
                } catch (Exception exception) {
                    // 记录失败记录
                    getSecurityContext().getAccountLoader().loginUnsuccessful(strings[0], exception.getMessage());
                }
            }
        } catch (Exception ex) {
            //
        }
        return account;
    }

    /**
     * 查找功能匹配器
     *
     * @return
     */
    protected FunctionPermissionHandler findFunctionMatcher(AuthenticationHandler handler) {
        String path = handler.getRequestPath();
        String method = handler.getRequestMethod();
        //
        if ("".equals(path)) path = "/";
        // 加载功能列表
        return this.findFunctionMatcher(method, path);
    }

    /**
     * 查找功能匹配器
     *
     * @return
     */
    protected FunctionPermissionHandler findFunctionMatcher(String method, String path) {
        return getAuthPathMatcher().match(method, path);
    }

    /**
     * 加载系统配置的功能权限列表
     *
     * @return
     */
    protected List<FunctionPermission> loadFunctionPermissionList() {
        List<FunctionPermission> functionPermissionList = getSecurityContext().getAccountLoader().loadFunctionPermission();
        if (functionPermissionList == null) {
            functionPermissionList = new ArrayList<>();
        }
        if (getSecurityContext().getFunctionPermissionLoaders() != null) {
            for (FunctionPermissionLoader functionPermissionLoader : getSecurityContext().getFunctionPermissionLoaders()) {
                functionPermissionLoader.loading(functionPermissionList, getSecurityConfig());
            }
        }
        return functionPermissionList;
    }
}
